/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package terra.network;

import java.net.InetSocketAddress;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;

import terra.AdminCommandHandler;
import terra.database.DatabaseFactory;
import terra.managers.CraftManager;
import terra.managers.MapManager;
import terra.managers.ThreadPoolManager;
import terra.model.CraftItem;
import terra.model.World;
import terra.model.map.Map;
import terra.model.object.Player;
import terra.model.object.item.Item;
import terra.network.handler.ClientHandler;
import terra.network.packet.Packet;
import terra.network.packet.client.ClientResolution;
import terra.network.packet.client.PlayerControl;
import terra.network.packet.client.RequestChatMessage;
import terra.network.packet.client.RequestCraftItem;
import terra.network.packet.client.RequestDropItem;
import terra.network.packet.client.RequestLogIn;
import terra.network.packet.client.RequestUseItem;
import terra.network.packet.server.ChatMessage;
import terra.network.packet.server.LoginAnswer;

/**
 * Handles both client-side and server-side handler depending on which
 * constructor was called.
 */
public class PlayerHandler extends ClientHandler {

	private static final Logger logger = Logger.getLogger(PlayerHandler.class
			.getName());
	private Player player;
	private int accountId;
	private String name;
	private int id;
	private int clientWidth = 800;
	private int clientHeight = 600;

	@Override
	public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
		super.channelConnected(ctx, e);
		Connections.connections.put(getChannel().getId(), this);
	}

	@Override
	public void channelDisconnected(ChannelHandlerContext ctx,
			ChannelStateEvent e) {
		super.channelDisconnected(ctx, e);
		Connections.connections.remove(getChannel().getId());
		if (player != null) {
			player.onDisconnect();
			World.getInstance().removeObject(player);
			Connection con = null;
			PreparedStatement statement = null;
			try {
				con = DatabaseFactory.getInstance().getConnection();
				statement = con
						.prepareStatement("UPDATE account SET online=0 WHERE accountname=?");
				statement.setString(1, getName());
				statement.execute();
			} catch (Exception ex) {
				ex.printStackTrace();
			} finally {
				try {
					con.close();
				} catch (SQLException ex) {
					// TODO Auto-generated catch block
					ex.printStackTrace();
				}
			}
		}
		try {
			this.finalize();
		} catch (Throwable e1) {
			e1.printStackTrace();
		}

	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
		logger.log(Level.WARNING, "Unexpected exception from downstream.",
				e.getCause());
	}

	/*
	 * 0 Ping 1 RequestLogIn 2 ClientResolution 3 PlayerControl 4
	 * RequestChatMessage
	 */

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
			throws Exception {
		super.messageReceived(ctx, e);
		// System.out.println("read "+ e.getMessage());
	}

	@Override
	public void handlePacket(Packet p) {
		switch (p.getId()) {
		case 0:
			send(p);
			break;
		case 1:
			readRequestLogIn((RequestLogIn) p);
			break;
		case 2:
			ClientResolution res = (ClientResolution) p;
			setClientWidth(res.w);
			setClientHeight(res.h);
			if (player != null)
				player.getKnownList().knowDistance = getClientWidth()
						+ getClientHeight() / 2;
			break;
		case 3:
			if (player != null)
				player.control = (PlayerControl) p;
			break;
		case 4:
			readRequestChatMessage((RequestChatMessage) p);
			break;
		case 18:
			readRequestUseItem((RequestUseItem) p);
			break;
		case 19:
			readRequestDropItem((RequestDropItem) p);
			break;
		case 24:
			readRequestCraftItem((RequestCraftItem) p);
			break;
		}
		p.delete();
	}

	private void readRequestCraftItem(RequestCraftItem p) {
		if (p.playerId != player.getObjectId())
			return;
		ArrayList<CraftItem> ingrids = new ArrayList<CraftItem>();
		for (int i = 0; i < RequestCraftItem.COUNT; i++) {
			if (p.itemOids[i] == -1)
				continue;
			ingrids.add(new CraftItem(p.itemOids[i], p.itemIds[i],
					p.itemCounts[i]));
		}
		CraftManager.getInstance().tryCraft(ingrids, player, false);
	}

	private void readRequestDropItem(RequestDropItem p) {
		player.inv.dropItem((int) (player.getX() - clientWidth / 2 + p.x),
				(int) (player.getY() - clientHeight / 2 + p.y), p.oId, p.count);
	}

	private void readRequestUseItem(RequestUseItem p) {
		if (player.getObjectId() != p.playerId)
			return;
		Item it = player.inv.getItemByObjectId(p.oId);
		if (it != null) {
			player.useItem(it);
		}
	}

	public Player getPlayer() {
		return player;
	}

	public void setPlayer(Player player) {
		this.player = player;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public int getClientWidth() {
		return clientWidth;
	}

	public void setClientWidth(int clientWidth) {
		this.clientWidth = clientWidth;
	}

	public int getClientHeight() {
		return clientHeight;
	}

	public void setClientHeight(int clientHeight) {
		this.clientHeight = clientHeight;
	}

	void onEnterWorld() {
		try {
			sendSystemMessage("Добро пожаловать на сервер !");
			sendSystemMessage("Текущий онлайн: "
					+ Connections.connections.size() + " !");
			sendWorld();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void sendWorld() {
		player.getMap().sendMap(player, false);
		player.getMap().getLeftMap().sendMap(player, false);
		player.getMap().getRightMap().sendMap(player, false);
		for (Map m : MapManager.getInstance().getMaps().values()) {
			if (m == player.getMap() || m == player.getMap().getLeftMap()
					|| m == player.getMap().getRightMap())
				continue;
			m.sendMap(player, false);
			 try { Thread.sleep(500); } catch (InterruptedException e) {
			 e.printStackTrace();}
		}
	}

	private void readRequestChatMessage(RequestChatMessage packet) {
		if (packet.objectId != player.getObjectId())
			return;
		if (packet.message.startsWith("admin_")) {
			AdminCommandHandler.handleCommand(packet.message, player);
			return;
		}
		ChatMessage cm = new ChatMessage(player.getObjectId(), packet.message);
		player.broadcastPacket(cm, true);
	}

	private void readRequestLogIn(RequestLogIn request) {
		if (player != null)
			return;
		if (!MapManager.getInstance().init)
			return;
		byte answer = LoginAnswer.LOGIN_FAIL;
		try (Connection con = DatabaseFactory.getInstance().getConnection();

				PreparedStatement statement = con
						.prepareStatement("SELECT id,password,online FROM account WHERE accountname=?")) {
			statement.setString(1, request.name);
			ResultSet rs = statement.executeQuery();
			rs.next();
			if (rs.first()) {
				if (!rs.getString("password").equals(request.pass)) {
					answer = LoginAnswer.LOGIN_FAIL;
				} else if (rs.getInt("online") != 0) {
					answer = LoginAnswer.LOGIN_CURRENT_IS_ONLINE;
				} else {
					answer = LoginAnswer.LOGIN_ACCEPT;
					setAccountId(Integer.parseInt(rs.getString("id")));
				}
			} else {
				answer = LoginAnswer.LOGIN_NO_ACC;
			}
			rs.close();
			statement.close();
		} catch (Exception e) {
			e.printStackTrace();
		}

		setName(request.name);
		send(new LoginAnswer(answer, 1));

		if (answer == LoginAnswer.LOGIN_ACCEPT) {
			updateAccountInfo();
			loadPlayer();
			ThreadPoolManager.getInstance().scheduleGeneral(new Runnable() {
				@Override
				public void run() {
					onEnterWorld();
				}
			}, 500);
		}
	}

	private void loadPlayer() {
		Player player = null;
		try (Connection con = DatabaseFactory.getInstance().getConnection();
				PreparedStatement statement = con
						.prepareStatement("SELECT * FROM characters WHERE accountId=?")) {
			statement.setInt(1, getAccountId());
			ResultSet rs = statement.executeQuery();
			rs.next();
			if (rs.first()) {
				player = Player.loadPlayer(rs, this);

			} else {
				player = new Player(World.getNextId(), 0, 64, 16, 31, this);

				player.inv.pickupItem(20, 100);
				player.inv.pickupItem(21, 100);
				player.inv.pickupItem(2, 1);
				player.inv.pickupItem(3, 1);
				player.inv.pickupItem(5, 1);
				player.inv.pickupItem(34, 10);
				player.insertPlayer();
			}
			rs.close();
			statement.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		setPlayer(player);
		getPlayer().sendPlayerInfo = true;
	}

	private void updateAccountInfo() {
		Connection con = null;
		PreparedStatement statement = null;
		try {
			con = DatabaseFactory.getInstance().getConnection();
			statement = con
					.prepareStatement("UPDATE account SET lastIP=?,last_entered=?,online=1 WHERE accountname=?");
			statement.setString(1, ((InetSocketAddress) getChannel()
					.getRemoteAddress()).getHostName());
			statement.setLong(2, System.currentTimeMillis());
			statement.setString(3, getName());
			statement.execute();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				con.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public void sendSystemMessage(String message) {
		ChatMessage req = new ChatMessage();
		req.oId = -1;
		req.message = message;
		send(req);
	}

	public int getAccountId() {
		return accountId;
	}

	public void setAccountId(int accountId) {
		this.accountId = accountId;
	}

}
